/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Collections.Specialized;
using System.Collections.ObjectModel;
using System.Collections;
using System.ComponentModel;

namespace Xceed.Silverlight.DragDrop
{
  public sealed class DragDropContext : DependencyObject, INotifyPropertyChanged
  {
    public DragDropContext( FrameworkElement draggedElement, DraggedElementConfiguration configuration, IEnumerable<FrameworkElement> siblingDraggedElements )
    {
      if( draggedElement == null )
        throw new ArgumentNullException( "draggedElement" );

      if( configuration == null )
        throw new ArgumentNullException( "configuration" );

      this.DraggedElement = draggedElement;
      m_siblingCollection = new SiblingsCollection( siblingDraggedElements );
      m_siblingCollection.CollectionChanged += new NotifyCollectionChangedEventHandler( OnSiblingsCollectionChanged );

      // Setting the configuration will set the CanDropCursor, CanDropCursortemplate,
      // NoDropCursor, and NoDropCursorTemplate properties.
      this.Configuration = configuration;
    }

    public DragDropContext( FrameworkElement draggedElement,
                            DraggedElementConfiguration configuration )
      : this( draggedElement, configuration, null )
    {
      //Nothing to do, calling another ctor with appropriate initialization values.
    }

    public DragDropContext( FrameworkElement draggedElement,
                            FrameworkElement draggedElementRoot,
                            IEnumerable<FrameworkElement> siblingDraggedElements,
                            DraggedElementConfiguration configuration,
                            object data )
      : this( draggedElement, configuration, siblingDraggedElements )
    {
      this.DraggedElementRoot = draggedElementRoot;
      this.Data = data;
    }

    #region CurrentDropTarget Property

    public IDropTarget CurrentDropTarget
    {
      get
      {
        if( DragDropManager.IsDragActive && ( DragDropManager.CurrentDragDropContext == this ) )
        {
          return DragDropManager.DraggedOverReference;
        }

        return null;
      }
    }

    #endregion

    #region LastMouseMoveEventArgs Property

    public MouseEventArgs LastMouseMoveEventArgs
    {
      get;
      internal set;
    }

    #endregion

    #region MouseOffset Property

    public Point MouseOffset
    {
      get;
      internal set;
    }

    #endregion

    #region Configuration Property

    public DraggedElementConfiguration Configuration
    {
      get
      {
        return m_configuration;
      }
      internal set
      {
        if( m_configuration != value )
        {
          m_configuration = value;

          this.CanDropCursor = m_configuration.CanDropCursor;
          this.NoDropCursor = m_configuration.NoDropCursor;
          this.NoDropCursorTemplate = m_configuration.NoDropCursorTemplate;
          this.CanDropCursorTemplate = m_configuration.CanDropCursorTemplate;
        }
      }
    }

    #endregion

    #region ParentDragContext Attached Property

    public static readonly DependencyProperty ParentDragContextProperty = DependencyProperty.RegisterAttached(
      "ParentDragContext",
      typeof( DragDropContext ),
      typeof( DragDropContext ),
      new PropertyMetadata( ( DragDropContext )null ) );

    public static DragDropContext GetParentDragContext( DependencyObject obj )
    {
      return ( DragDropContext )obj.GetValue( DragDropContext.ParentDragContextProperty );
    }

    internal static void SetParentDragContext( DependencyObject obj, DragDropContext value )
    {
      obj.SetValue( DragDropContext.ParentDragContextProperty, value );
    }

    #endregion

    #region CanDropCursor Property

    public DragDropCursor CanDropCursor
    {
      get;
      set;
    }

    #endregion

    #region CanDropCursorTemplate Property

    public DataTemplate CanDropCursorTemplate
    {
      get;
      set;
    }

    #endregion

    #region NoDropCursor Property

    public DragDropCursor NoDropCursor
    {
      get;
      set;
    }

    #endregion

    #region NoDropCursorTemplate Property

    public DataTemplate NoDropCursorTemplate
    {
      get;
      set;
    }

    #endregion

    #region DraggedElement Property

    public FrameworkElement DraggedElement
    {
      get;
      internal set;
    }

    #endregion

    #region DraggedElementRoot Property

    public FrameworkElement DraggedElementRoot
    {
      get;
      internal set;
    }

    #endregion

    #region SiblingDraggedElements Property

    public SiblingsCollection SiblingDraggedElements
    {
      get
      {
        return m_siblingCollection;
      }
    }

    private void OnSiblingsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      this.OnPropertyChanged( "SiblingsCount" );
    }

    private SiblingsCollection m_siblingCollection;

    #endregion

    #region SiblingsCount Property

    public int SiblingsCount
    {
      get
      {
        return m_siblingCollection.Count;
      }
    }

    #endregion

    #region Data Property

    public object Data
    {
      get
      {
        return m_data;
      }
      set
      {
        this.VerifyFrozen();

        if( value != m_data )
        {
          m_data = value;
        }
      }
    }

    private object m_data;

    #endregion

    #region IsFrozen Property

    internal bool IsFrozen
    {
      get;
      private set;
    }

    #endregion

    #region DragStarting Event

    public event EventHandler<DragDropCancelEventArgs> DragStarting;

    internal void OnDragStarting( DragDropCancelEventArgs e )
    {
      if( this.DragStarting != null )
      {
        this.DragStarting( this, e );
      }
    }

    #endregion

    #region DragStarted Event

    public event EventHandler DragStarted;

    internal void OnDragStarted()
    {
      if( this.DragStarted != null )
      {
        this.DragStarted( this, EventArgs.Empty );
      }
    }

    #endregion

    #region DragMove Event

    public event EventHandler<DragDropMoveEventArgs> DragMove;

    internal void OnDragMove( DragDropMoveEventArgs e )
    {
      if( this.DragMove != null )
      {
        this.DragMove( this, e );
      }
    }

    #endregion

    #region DragCompleted Event

    public event EventHandler<DragDropEventArgs> DragCompleted;

    internal void OnDragCompleted( DragDropEventArgs e )
    {
      if( this.DragCompleted != null )
      {
        this.DragCompleted( this, e );
      }
    }

    #endregion

    #region DragAborted Event

    public event EventHandler<DragDropEventArgs> DragAborted;

    internal void OnDragAborted( DragDropEventArgs e )
    {
      if( this.DragAborted != null )
      {
        this.DragAborted( this, e );
      }
    }

    #endregion

    #region DragPaused Event

    public EventHandler<DragDropEventArgs> DragPaused;

    internal void OnDragPaused( DragDropEventArgs e )
    {
      if( this.DragPaused != null )
      {
        this.DragPaused( this, e );
      }
    }

    #endregion

    #region DragResumed Event

    public event EventHandler<DragDropEventArgs> DragResumed;

    internal void OnDragResumed( DragDropEventArgs e )
    {
      if( this.DragResumed != null )
      {
        this.DragResumed( this, e );
      }
    }

    #endregion

    #region Internal Methods

    internal DragDropCursor GetCursor( bool canDrop )
    {
      if( canDrop )
      {
        return this.CanDropCursor;
      }
      else
      {
        return this.NoDropCursor;
      }
    }

    internal DataTemplate GetCustomCursorTemplate( bool canDrop )
    {
      if( canDrop )
      {
        return this.CanDropCursorTemplate;
      }
      else
      {
        return this.NoDropCursorTemplate;
      }
    }

    internal void FreezeDragContext()
    {
      this.Freeze();
      m_siblingCollection.Freeze();
    }

    private void Freeze()
    {
      this.IsFrozen = true;
    }

    private void VerifyFrozen()
    {
      if( this.IsFrozen )
        throw new InvalidOperationException( "An attempt was made to modify the Data property of the DragDropContext after the drag-drop operation has started." );
    }

    #endregion

    #region Private Fields

    private DraggedElementConfiguration m_configuration;

    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged( string propertyName )
    {
      if( this.PropertyChanged != null )
      {
        this.PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
      }
    }

    #endregion

    #region Sub-Classes

    public class SiblingsCollection : IList<FrameworkElement>, INotifyCollectionChanged
    {
      internal SiblingsCollection( IEnumerable<FrameworkElement> startingElements )
      {
        m_innerCollection = new ObservableCollection<FrameworkElement>();

        if( startingElements != null )
        {
          using( IEnumerator<FrameworkElement> enumerator = startingElements.GetEnumerator() )
          {
            while( enumerator.MoveNext() )
            {
              m_innerCollection.Add( enumerator.Current );
            }
          }
        }

        m_innerCollection.CollectionChanged += new NotifyCollectionChangedEventHandler( OnInnerCollectionChanged );
      }

      #region IsFrozen Property

      internal bool IsFrozen
      {
        get;
        private set;
      }

      #endregion

      internal void Freeze()
      {
        this.IsFrozen = true;
      }

      private void VerifyFrozen()
      {
        if( this.IsFrozen )
          throw new InvalidOperationException( "An attempt was made to modify the SiblingDraggedElements collection after the drag-drop operation has started." );
      }

      private void OnInnerCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
      {
        if( this.CollectionChanged != null )
        {
          this.CollectionChanged( this, e );
        }
      }

      #region IList<FrameworkElement> Members

      public int IndexOf( FrameworkElement item )
      {
        return m_innerCollection.IndexOf( item );
      }

      public void Insert( int index, FrameworkElement item )
      {
        this.VerifyFrozen();

        m_innerCollection.Insert( index, item );
      }

      public void RemoveAt( int index )
      {
        this.VerifyFrozen();

        m_innerCollection.RemoveAt( index );
      }

      public FrameworkElement this[ int index ]
      {
        get
        {
          return m_innerCollection[ index ];
        }
        set
        {
          this.VerifyFrozen();

          m_innerCollection[ index ] = value;
        }
      }

      #endregion

      #region ICollection<FrameworkElement> Members

      public void Add( FrameworkElement item )
      {
        this.VerifyFrozen();

        m_innerCollection.Add( item );
      }

      public void Clear()
      {
        this.VerifyFrozen();

        m_innerCollection.Clear();
      }

      public bool Contains( FrameworkElement item )
      {
        return m_innerCollection.Contains( item );
      }

      public void CopyTo( FrameworkElement[] array, int arrayIndex )
      {
        m_innerCollection.CopyTo( array, arrayIndex );
      }

      public int Count
      {
        get
        {
          return m_innerCollection.Count;
        }
      }

      public bool IsReadOnly
      {
        get
        {
          return this.IsFrozen;
        }
      }

      public bool Remove( FrameworkElement item )
      {
        this.VerifyFrozen();

        return m_innerCollection.Remove( item );
      }

      #endregion

      #region IEnumerable<FrameworkElement> Members

      public IEnumerator<FrameworkElement> GetEnumerator()
      {
        return m_innerCollection.GetEnumerator();
      }

      #endregion

      #region IEnumerable Members

      System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
      {
        return ( ( IEnumerable )m_innerCollection ).GetEnumerator();
      }

      #endregion

      #region INotifyCollectionChanged Members

      public event NotifyCollectionChangedEventHandler CollectionChanged;

      #endregion

      private ObservableCollection<FrameworkElement> m_innerCollection;
    }

    #endregion
  }
}
